package com.sourcespro;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.sourcespro.db.DBColumn;
import com.sourcespro.db.DBUtils;
import com.sourcespro.domain.JavaField;
import com.sourcespro.domain.MapperColumn;
import com.sourcespro.ui.InfoDialog;
import com.sourcespro.util.DateUtils;
import com.sourcespro.util.FieldUtils;
import com.sourcespro.util.MapperXmlUtils;
import com.sourcespro.util.VelocityUtil;
import org.apache.commons.io.FileUtils;
import org.jdom.Document;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * genCode
 *
 * @author zhanghaowei
 * @date 2018/6/4
 */
public class MainAction extends AnAction {

    private static final Logger logger = LoggerFactory.getLogger(MainAction.class);

    private Project project;
    private String packageName = "";
    private String author;
    private String moduleName;
    private String className;
    private String tableName;
    private String tableAlias;
    private String description;
    private Boolean isType;
    private Boolean genController;
    private Boolean genService;
    private Boolean genMapper;
    private Boolean genMapperXml;
    private Boolean genApiNote;
    private Boolean genServiceImpl;
    private Boolean genEntity;
    private Boolean genLombok;
    private Boolean genImport;
    private Boolean genExport;

    private List<JavaField> fieldList;
    private List<MapperColumn> mapperColumnList;
    private Set<String> importList;

    private enum CodeType {
        /**
         * 生成类型
         */
        Entity, Controller, ControllerNote, Service, ServiceImpl, Mapper, MapperXml
    }

    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        project = e.getData(LangDataKeys.PROJECT);
        init(e);
    }

    /**
     * 刷新项目
     */
    private void refreshProject(AnActionEvent e) {
        Project project = e.getProject();
        if (project != null) {
            VirtualFile virtualFile = ProjectUtil.guessProjectDir(project);
            if (virtualFile != null) {
                virtualFile.refresh(false, true);
            }
        }
    }

    /**
     * 初始化Dialog
     */
    private void init(AnActionEvent e) {
        InfoDialog myDialog = new InfoDialog(genBean -> {
            MainAction.this.moduleName = genBean.getModuleName();
            MainAction.this.author = genBean.getAuthor();
            MainAction.this.packageName = genBean.getPackageName();
            MainAction.this.className = genBean.getClassName();
            MainAction.this.tableName = genBean.getTableName();
            MainAction.this.description = genBean.getDescription();
            MainAction.this.tableAlias = genBean.getTableAlias();
            MainAction.this.isType = genBean.getType();
            MainAction.this.genController = genBean.getGenController();
            MainAction.this.genService = genBean.getGenService();
            MainAction.this.genMapper = genBean.getGenMapper();
            MainAction.this.genMapperXml = genBean.getGenMapperXml();
            MainAction.this.genApiNote = genBean.getGenApiNote();
            MainAction.this.genServiceImpl = genBean.getGenServiceImpl();
            MainAction.this.genEntity = genBean.getGenEntity();
            MainAction.this.genLombok = genBean.getGenLombok();
            MainAction.this.genImport = genBean.getGenImport();
            MainAction.this.genExport = genBean.getGenExport();

            initEnv();
            getFieldsFromDB();
            createClassFiles();
            refreshProject(e);
            Messages.showInfoMessage(project, "create code success", "成功");
        });
        myDialog.setVisible(true);
    }

    private void initEnv() {
        Env.projectPath = project.getBasePath();
        Env.resourcesPath = getResourcesPath();
        Env.packageName = packageName;
        Env.author = author;
        Env.className = className;
        Env.description = description;
        Env.moduleName = moduleName;
        Env.tableName = tableName;
        Env.tableAlias = tableAlias;
    }

    /**
     * 通过数据库获取字段信息
     */
    private void getFieldsFromDB() {
        List<DBColumn> columnList = DBUtils.getColumnList(tableName);
        fieldList = new ArrayList<>();
        mapperColumnList = new ArrayList<>();
        importList = new HashSet<>();
        for (DBColumn column : columnList) {
            JavaField field = new JavaField();
            field.setName(FieldUtils.camel(column.getName()));
            field.setUpName(FieldUtils.UpCamel(column.getName()));
            String type = FieldUtils.type(column.getType());
            if ("Date".equals(type)){
                importList.add("import java.util.Date;");
            }
            if ("BigDecimal".equals(type)){
                importList.add("import java.math.BigDecimal;");
            }
            field.setType(type);
            field.setNote(column.getNote());
            fieldList.add(field);

            MapperColumn mc = new MapperColumn();
            mc.setColumn(column.getName());
            mc.setJdbcType(FieldUtils.jdbcType(column.getType()));
            mc.setProperty(field.getName());
            mapperColumnList.add(mc);

            if (column.isPk()){
                if ("INTEGER".equalsIgnoreCase(mc.getJdbcType())) {
                    Env.idType = "java.lang.Integer";
                    Env.jdbcType = "INTEGER";
                } else if ("BIGINT".equalsIgnoreCase(mc.getJdbcType())){
                    Env.idType = "java.lang.Long";
                    Env.jdbcType = "BIGINT";
                } else if ("VARCHAR".equalsIgnoreCase(mc.getJdbcType())){
                    Env.idType = "java.lang.String";
                    Env.jdbcType = "VARCHAR";
                } else if ("LONGVARCHAR".equalsIgnoreCase(mc.getJdbcType())){
                    Env.idType = "java.lang.String";
                    Env.jdbcType = "VARCHAR";
                }
            }
        }
    }

    /**
     * 生成类文件
     */
    private void createClassFiles() {
        if (genEntity) {
            createClassFile(CodeType.Entity);
        }
        if (genController) {
            createClassFile(CodeType.Controller);
        }
        if (genApiNote) {
            createClassFile(CodeType.ControllerNote);
        }
        if (genService) {
            createClassFile(CodeType.Service);
        }
        if (genServiceImpl) {
            createClassFile(CodeType.ServiceImpl);
        }
        if (genMapper) {
            createClassFile(CodeType.Mapper);
        }
        if (genMapperXml) {
            createClassFile(CodeType.MapperXml);
        }
    }

    /**
     * 生成代码
     */
    private void createClassFile(CodeType codeType) {
        String fileName;
        String content;
        String appPath = getAppPath();
        String appMapperPath = getAppMapperPath();
        switch (codeType) {
            case Entity:
                String addDtoName;
                String editDtoName;
                String importExcelName;
                String exportExcelName;
                String addDtoContent;
                String editDtoContent;
                String importExcelContent;
                String exportExcelContent;
                if (genLombok) {
                    fileName = "TemplateEntityLombok.vm";
                    addDtoName = "TemplateAddEntityLombok.vm";
                    editDtoName = "TemplateEditEntityLombok.vm";
                    importExcelName = "TemplateImportExcelEntityLombok.vm";
                    exportExcelName = "TemplateExportExcelEntityLombok.vm";
                } else {
                    fileName = "TemplateEntity.vm";
                    addDtoName = "TemplateAddEntity.vm";
                    editDtoName = "TemplateEditEntity.vm";
                    importExcelName = "TemplateImportExcelEntity.vm";
                    exportExcelName = "TemplateExportExcelEntity.vm";
                }
                //entity
                content = readtemplatefile(fileName);
                content = VelocityUtil.evaluate(content, fieldList, importList);
                writeToFile(content, appPath + "entity", className + ".java");
                //addDto
                addDtoContent = readtemplatefile(addDtoName);
                addDtoContent = VelocityUtil.evaluate(addDtoContent, fieldList, importList);
                writeToFile(addDtoContent, appPath + "crudparams/dto", "Add" + className + "Dto.java");
                //editDto
                editDtoContent = readtemplatefile(editDtoName);
                editDtoContent = VelocityUtil.evaluate(editDtoContent, fieldList, importList);
                writeToFile(editDtoContent, appPath + "crudparams/dto", "Edit" + className + "Dto.java");
                //ImportExcelModel
                if (genImport) {
                    importExcelContent = readtemplatefile(importExcelName);
                    importExcelContent = VelocityUtil.evaluate(importExcelContent, fieldList, importList);
                    writeToFile(importExcelContent, appPath + "crudparams/excel", className + "ImportExcel.java");
                }
                //ExportExcelModel
                if (genExport) {
                    exportExcelContent = readtemplatefile(exportExcelName);
                    exportExcelContent = VelocityUtil.evaluate(exportExcelContent, fieldList, importList);
                    writeToFile(exportExcelContent, appPath + "crudparams/excel", className + "ExportExcel.java");
                }
                break;
            case Controller:
                if (isType) {
                    fileName = "TemplateTypeController.vm";
                } else {
                    fileName = "TemplateController.vm";
                }
                content = readtemplatefile(fileName);
                Map<String, Object> params = new HashMap<>(2);
                params.put("genImport", genImport);
                params.put("genExport", genExport);
                content = VelocityUtil.evaluate(content, params);
                writeToFile(content, appPath + "controller", className + "Controller.java");
                break;
            case ControllerNote:
                if (isType) {
                    fileName = "TemplateTypeNote.vm";
                } else {
                    fileName = "TemplateNote.vm";
                }
                content = readtemplatefile(fileName);
                content = VelocityUtil.evaluate(content, fieldList, importList);
                writeToFile(content, appPath + "crudparams/swaggernotes", className + "Notes.java");
                break;
            case Service:
                if (isType) {
                    fileName = "TemplateTypeService.vm";
                } else {
                    fileName = "TemplateService.vm";
                }
                content = readtemplatefile(fileName);
                content = VelocityUtil.evaluate(content, Collections.emptyMap());
                writeToFile(content, appPath + "service", className + "Service.java");
                break;
            case ServiceImpl:
                if (isType) {
                    fileName = "TemplateTypeServiceImpl.vm";
                } else {
                    fileName = "TemplateServiceImpl.vm";
                }
                content = readtemplatefile(fileName);
                content = VelocityUtil.evaluate(content, Collections.emptyMap());
                writeToFile(content, appPath + "service/impl", className + "ServiceImpl.java");
                break;
            case Mapper:
                if (isType) {
                    fileName = "TemplateTypeMapper.vm";
                } else {
                    fileName = "TemplateMapper.vm";
                }
                content = readtemplatefile(fileName);
                content = VelocityUtil.evaluate(content, Collections.emptyMap());
                writeToFile(content, appPath + "dao", className + "Mapper.java");
                break;
            case MapperXml:
                if (isType) {
                    fileName = "TemplateTypeMapperXml.vm";
                } else {
                    fileName = "TemplateMapperXml.vm";
                }
                String baseColumn = MapperXmlUtils.getBaseColumn(mapperColumnList);
                String columnNoId = MapperXmlUtils.getBaseColumnNoId(mapperColumnList);
                String insertVals = MapperXmlUtils.getInsertVals(mapperColumnList);
                String batchInsertVals = MapperXmlUtils.getBatchInsertVals(mapperColumnList);
                String updateVals = MapperXmlUtils.getUpdateVals(mapperColumnList);
                content = readtemplatefile(fileName);

                Map<String, Object> mapperMap = new HashMap<>();
                mapperMap.put("list", mapperColumnList);
                mapperMap.put("baseColumn", baseColumn);
                mapperMap.put("columnNoId", columnNoId);
                mapperMap.put("insertVals", insertVals);
                mapperMap.put("batchInsertVals", batchInsertVals);
                mapperMap.put("updateVals", updateVals);
                mapperMap.put("idType", Env.idType);
                mapperMap.put("jdbcType", Env.jdbcType);
                mapperMap.put("tableAlias", Env.tableAlias);
                content = VelocityUtil.evaluate(content, mapperMap);
                writeToFile(content, appMapperPath, className + "Mapper.xml");
                break;
            default:
                System.out.println("err");
        }
    }

    /**
     * 获取包名文件路径
     */
    private String getAppPath() {
        String packagePath = packageName.replace(".", "/");
        String appPath;
        if (moduleName != null && !"".equals(moduleName)) {
            appPath = project.getBasePath() + "/" + moduleName + "/src/main/java/" + packagePath + "/";
        } else {
            appPath = project.getBasePath() + "/src/main/java/" + packagePath + "/";
        }
        return appPath;
    }

    /**
     * 获取Mapper文件路径
     */
    private String getAppMapperPath() {
        return getResourcesPath() + "mapper/";
    }

    /**
     * 获取resources文件路径
     */
    private String getResourcesPath() {
        String resourcesPath;
        if (moduleName != null && !"".equals(moduleName)) {
            resourcesPath = project.getBasePath() + "/" + moduleName + "/src/main/resources/";
        } else {
            resourcesPath = project.getBasePath() + "/src/main/resources/";
        }
        return resourcesPath;
    }

    /**
     * 替换模板中字符
     */
    private String dealTemplateContent(String content) {
        if (content.contains("$packageName")) {
            content = content.replace("$packageName", packageName);
        }
        if (content.contains("$className")) {
            content = content.replace("$className", className);
        }
        content = content.replace("$description", description);
        content = content.replace("$author", author);
        content = content.replace("$date", DateUtils.getDate());
        return content;
    }

    /**
     * 读取模板文件中的字符内容
     *
     * @param fileName 模板文件名
     */
    private String readtemplatefile(String fileName) {
        InputStream in = null;
        in = this.getClass().getResourceAsStream("/template/" + fileName);
        String content = "";
        try {
            content = new String(readStream(in), StandardCharsets.UTF_8);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return content;
    }

    private Document readMapperXml(String fileName) {
        String appMapperPath = getAppMapperPath();
        SAXBuilder saxBuilder = new SAXBuilder();
        try {
            return saxBuilder.build(new File(appMapperPath + fileName));
        } catch (Exception e) {
            logger.error("解析mapper文件错误：{}", e);
        }
        return null;
    }

    private byte[] readStream(InputStream inputStream) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = -1;
        try {
            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            outputStream.close();
            inputStream.close();
        }
        return outputStream.toByteArray();
    }

    /**
     * 生成
     *
     * @param content   类中的内容
     * @param classPath 类文件路径
     * @param className 类文件名称
     */
    private void writeToFile(String content, String classPath, String className) {
        try {
            File floder = new File(classPath);
            if (!floder.exists()) {
                floder.mkdirs();
            }
            File file = new File(classPath + "/" + className);
            if (!file.exists()) {
                file.createNewFile();
            }

            Writer writer = new BufferedWriter(
                    new OutputStreamWriter(
                            new FileOutputStream(file), StandardCharsets.UTF_8));
            writer.write(content);
            writer.flush();
            writer.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成
     *
     * @param content    Mapper中的内容
     * @param mapperPath Mapper文件路径
     * @param mapperName Mapper文件名称
     */
    private void writeMapperToFile(Document content, String mapperPath, String mapperName) {
        try {
            File floder = new File(mapperPath);
            if (!floder.exists()) {
                floder.mkdirs();
            }
            XMLOutputter xmlOutputter = new XMLOutputter();
            OutputStream outputStream = new FileOutputStream(mapperPath + "/" + mapperName);
            xmlOutputter.output(content, outputStream);
            logger.info("mapper生成成功");
            outputStream.close();
            //替换 &lt; &gt; 为 < >
            File preReplaceFile = new File(mapperPath + "/" + mapperName);
            String originFile = FileUtils.readFileToString(preReplaceFile, "UTF-8");
            String newFile = originFile.replaceAll("&lt;", "<").replaceAll("&gt;", ">");
            FileUtils.writeStringToFile(preReplaceFile, newFile, "UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
